from django.shortcuts import render, HttpResponse
from django.db.models import Q, F, Count, Prefetch
from django.db import connection
from .models import Book, BookOrder, Publisher


def index(request):
    # return HttpResponse('success')
    return render(request, 'index.html')


def index2(request):
    # TODO: 链式调用

    # 1.filter：将满足条件的数据提取出来，返回一个新的QuerySet
    # ID大等于2且排除ID为3的图书
    # books = Book.objects.filter(id__gte=2).filter(~Q(id=3))

    # 2.exclude：排除满足条件的数据，返回一个新的QuerySet
    # ID大等于2且排除ID为3的图书
    # books = Book.objects.filter(id__gte=2).exclude(id=3)

    # 3.annotate：给QuerySet中的每个对象都添加一个使用查询表达式（聚合函数、F表达式、Q表达式、Func表达式等）的新字段
    # 查询每本图书的作者
    books = Book.objects.annotate(author_name=F('author__name'))
    for book in books:
        print('name:%s, author:%s' % (book.name, book.author_name))
    print(books.query)
    return HttpResponse('index2')


def index3(request):
    """
    order_by:
    1.指定将查询的结果根据某个字段进行排序。
    2.如果要倒叙排序，那么可以在这个字段的前面加一个负号
    """
    # 1.订单：根据create_time对从小到大进行排序
    # orders = BookOrder.objects.order_by('create_time')

    # 2.订单：根据create_time对从大到小进行排序
    # orders = BookOrder.objects.order_by('-create_time')

    # 3.订单：首先根据create_time从大到小进行排序，如果create_time是一样的那么根据price从大到小进行排序
    # orders = BookOrder.objects.order_by('-create_time', '-price')

    # 4.订单：要根据订单的图书的评分来进行排序(从小到大)
    # orders = BookOrder.objects.order_by('book__rating')
    #
    # for order in orders:
    #     print(order)
    # print(orders.query)
    # 5.图书：提取图书数据，根据图书的销量进行排序（从大到小进行排序）
    # books = Book.objects.annotate(salecount=Count('bookorder__id')).order_by('-salecount')
    # for book in books:
    #     print('name=%s, price=%s, salecount=%s' % (book.name, book.price, book.salecount))
    # print(books.query)

    orders = BookOrder.objects.all()
    for order in orders:
        print(order)
    print(orders.query)
    return HttpResponse('index3')


def index4(request):
    """
    values: 用来指定在提取数据出来，需要提取哪些字段。默认情况下会把表中所有的字段全部都提取出来，可以使用values来进行指定，
    并且使用了values方法后，提取出的QuerySet中的数据类型不是模型，而是在values方法中指定的字段和值形成的字典
    """
    # 1.查询id, name, author__name
    # books = Book.objects.values('id', 'name', 'author__name')

    # 2.查询id, name, author_name指定为author__name
    # books = Book.objects.values('id', 'name', author_name=F('author__name'))

    # 3.如果在values中没有传递任何参数，那么将会返回这个模型中所有的属性
    # books = Book.objects.values()

    """
    values_list：类似于values。只不过返回的QuerySet中，存储的不是字典，而是元组
    """
    # 4.查询id, name, author__name
    # books = Book.objects.values_list('id', 'name', 'author__name')

    # 5.如果在values_list中只有一个字段。那么你可以传递flat=True来将结果扁平化
    # ('曹雪芹',)  => 曹雪芹
    books = Book.objects.values_list('author__name', flat=True)

    for book in books:
        print(book)
    print(books.query)
    return HttpResponse('index4')


def index5(request):
    """
    all：获取这个ORM模型的QuerySet对象
    """
    books = Book.objects.all()
    for book in books:
        print(book)
    print(books.query)
    return HttpResponse('index5')


def index6(request):
    """
    select_related: 在提取某个模型的数据的同时，也提前将相关联的数据提取出来。比如提取文章数据，
    可以使用select_related将author信息提取出来，以后再次使用book.author的时候就不需要再次去访问数据库了。
    可以减少数据库查询的次数。
    select_related只能用在一对多或者一对一中，不能用在多对多或者多对一中。
    比如可以通过订单获取图书 ，但是不能通过图书获取多个图书订单。
    """
    # books = Book.objects.all()

    # books = Book.objects.select_related('author', 'publisher')
    # for book in books:
    #     print('{name}, 作者名称：{author_name}，出版社：{pub_name}'.format(name=book.name, author_name=book.author.name,
    #                                                              pub_name=book.publisher.name))
    # print(connection.queries)
    # print(books.query)

    books = Book.objects.select_related('bookorder')
    for book in books:
        print(book)
    print(books.query)
    return HttpResponse('index6')


def index7(request):
    """
    prefetch_related:
    这个方法和select_related非常的类似，就是在访问多个表中的数据的时候，减少查询的次数。
    这个方法是为了解决多对一和多对多的关系的查询问题。
    """
    # books = Book.objects.prefetch_related('bookorders')
    # for book in books:
    #     print('=' * 30)
    #     print(book.name)
    #     for order in book.bookorders.all():
    #         print(order.id)
    # print(connection.queries)
    # print(books.query)

    # books = Book.objects.prefetch_related('author')
    # for book in books:
    #     print(book.author.name)
    # print(connection.queries)
    # print(books.query)

    prefetch = Prefetch('bookorders', queryset=BookOrder.objects.filter(price__gte=90))
    books = Book.objects.prefetch_related(prefetch)
    for book in books:
        print('=' * 30)
        print(book.name)
        orders = book.bookorders.all()
        for order in orders:
            print(order.id)
    print(connection.queries)
    print(books.query)
    return HttpResponse('index7')


def index8(request):
    # TODO: defer：
    # 使用defer来过滤掉一些字段。这个字段跟values有点类似，只不过defer返回的不是字典，而是模型
    # defer虽然能过滤字段，但是有些字段是不能过滤的，比如id，即使你过滤了，也会提取出来
    # books = Book.objects.defer('pages', 'id')
    # for book in books:
    #     print('%s/%s' % (book.id, book.name))
    #     # print(book.pages)

    # TODO: only
    # 跟defer类似，只不过defer是过滤掉指定的字段，而only是只提取指定的字段
    books = Book.objects.only('name')
    for book in books:
        print('%s/%s' % (book.id, book.name))
    print(books.query)
    print(connection.queries)
    return HttpResponse('index8')


def index9(request):
    # TODO: get
    # 获取满足条件的数据。这个函数只能返回一条数据，并且如果给的条件有多条数据，那么这个方法会抛出错误
    # 如果给的条件没有任何数据，那么就会抛出错误。所以这个方法在获取数据的只能，只能有且只有一条。
    book = Book.objects.get(id=1)
    print(book)
    print(connection.queries)
    return HttpResponse('index9')


def index10(request):
    # TODO: create
    # 创建一条数据，并且保存到数据库中。这个方法相当于先用指定的模型创建一个对象，然后再调用这个对象的save方法
    # publisher = Publisher(name='电子工业大学出版社')
    # publisher.save()

    publisher = Publisher.objects.create(name='工业大学出版社')
    print(connection.queries)
    return HttpResponse('index10')


def index11(request):
    # TODO: get_or_create
    # 根据某个条件进行查找，如果找到了那么就返回这条数据，如果没有查找到，那么就创建一个
    # 如果有，那么就会查找出来，如果没有，则会创建并且存储到数据库中。
    # 这个方法的返回值是一个元组，元组的第一个参数obj是这个对象，第二个参数created代表是否创建的
    # publisher = Publisher.objects.get_or_create(name='知了课堂出版社')
    # print(publisher[0])
    # print(connection.queries)

    # TODO: bulk_create
    publishers = Publisher.objects.bulk_create([
        Publisher(name='测试1出版社'),
        Publisher(name='测试2出版社')
    ])
    print(publishers)
    print(connection.queries)
    return HttpResponse('index11')


def index12(request):
    # TODO: count
    # 获取提取的数据的个数。如果想要知道总共有多少条数据，那么建议使用count，而不是使用len这种。
    # 因为count在底层是使用select count(*)来实现的，这种方式比使用len函数更加的高效。
    # books = Book.objects.all()
    # print(len(books))
    # print(books.query)

    # result = Book.objects.count()
    # print(result)

    # TODO: first和last：返回QuerySet中的第一条和最后一条数据
    # book = Book.objects.first()
    # book = Book.objects.last()
    # print(book)

    # TODO: aggregate: 使用聚合函数
    # books = Book.objects.aggregate(num=Count('id'))
    # print(books)

    # TODO: exists
    # 判断某个条件的数据是否存在。如果要判断某个条件的元素是否存在，那么建议使用exists，
    # 这比使用count或者直接判断QuerySet更有效得多。
    result = Book.objects.filter(name__contains='三国').exists()
    print(result)
    print(connection.queries)
    return HttpResponse('index12')


def index13(request):
    # TODO: distinct
    # 去除掉那些重复的数据。这个方法如果底层数据库用的是MySQL，那么不能传递任何的参数。
    # 比如想要提取所有销售的价格超过80元的图书，并且删掉那些重复的，那么可以使用distinct来帮我们实现

    books = Book.objects.filter(bookorders__price__gte=80).distinct()

    # 需要注意的是，如果在distinct之前使用了order_by，那么因为order_by会提取order_by中指定的字段，
    # 因此再使用distinct就会根据多个字段来进行唯一化，所以就不会把那些重复的数据删掉
    # books = Book.objects.filter(bookorders__price__gte=80).order_by('bookorders__price').distinct()
    for book in books:
        print(book)
    print(books.query)
    return HttpResponse('index13')


def index14(request):
    # TODO: 1.update
    # 执行更新操作，在SQL底层走的也是update命令
    # 注意这个方法走的是更新的逻辑。所以更新完成后保存到数据库中不会执行save方法，因此不会更新auto_now设置的字段
    # Book.objects.update(price=F('price') + 5)

    # TODO: 2.delete
    # 删除所有满足条件的数据。删除数据的时候，要注意on_delete指定的处理方式.
    Publisher.objects.filter(id__gte=3).delete()
    return HttpResponse('index14')


def index15(request):
    # TODO: 切片操作
    # 有时候我们查找数据，有可能只需要其中的一部分。那么这时候可以使用切片操作来帮我们完成。
    # QuerySet使用切片操作就跟列表使用切片操作是一样的
    books = Book.objects.all()[0:10]
    for book in books:
        print(book)
    print(books.query)
    return HttpResponse('index15')


def index16(request):
    # TODO: 什么时候Django会将QuerySet转换为SQL去执行
    # 生成一个QuerySet对象并不会马上转换为SQL语句去执行

    # 1.迭代：在遍历QuerySet对象的时候，会首先先执行这个SQL语句，然后再把这个结果返回进行迭代。
    # books = Book.objects.all()
    # for book in books:
    #     print(book)

    # 2.使用步长做切片操作:
    # QuerySet可以类似于列表一样做切片操作。做切片操作本身不会执行SQL语句，但是如果如果在做切片操作的时候提供了步长，
    # 那么就会立马执行SQL语句。需要注意的是，做切片后不能再执行filter方法，否则会报错。
    # books = Book.objects.all()[0:10:2]

    # 3.调用len函数：调用len函数用来获取QuerySet中总共有多少条数据也会执行SQL语句
    # books = Book.objects.all()
    # print(len(books))

    # 4.调用list函数：调用list函数用来将一个QuerySet对象转换为list对象也会立马执行SQL语句
    # books = Book.objects.all()
    # print(list(books))

    # 5.判断：如果对某个QuerySet进行判断，也会立马执行SQL语句
    books = Book.objects.all()
    if books:
        print('有数据...')
    print(connection.queries)
    return HttpResponse('index16')
